/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package analisadorSintatico;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import modelo.ErroSintatico;
import modelo.IErro;
import modelo.Token;
import modelo.tipos.TipoErroSintatico;
import modelo.tipos.TipoToken;

/**
 *
 * @author DEP
 */
public class AnalisadorSintatico {

    private List<IErro> errosSintaticos;
    private List<Token> tokens;
    private Stack<NoInterno> nos = null;
    private NoInterno raiz = null;
    private boolean estadoErro = false;

    public AnalisadorSintatico(List<Token> tokens) {
        this.tokens = tokens;
    }

    public NoInterno getRaiz() {
        return raiz;
    }

    public String getErrosSintaticos() {
        String erroString = "";
        for (IErro erro : errosSintaticos) {
            ErroSintatico erroSintatico = (ErroSintatico) erro;
            erroString += erroSintatico.errosToString();
        }
        return erroString;
    }

    public void analisar() {
        errosSintaticos = new ArrayList<>();
        try {
            nos = new Stack<>();
            programa();
        } catch (ErroSintatico ex) {
            errosSintaticos.add(ex);
        }
    }

    private void programa() throws ErroSintatico {
        raiz = new NoInterno("programa");
        nos.push(raiz);

        consumir(TipoToken.PALAVRA_RESERVADA, "programa");
        consumir(TipoToken.IDENTIFICADOR);
        consumir(TipoToken.PONTOEV);
        declaracoes();
        bloco();
        consumir(TipoToken.PONTO);
        nos.pop();
    }

    private void declaracoes() throws ErroSintatico {

        adicionarNo("declaracoes");
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
            declaracaoVariavel();
        }
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "constante")) {
            declaracaoConstante();
        }
        declProcedimento();
        removeNo();
    }

    private void declProcedimento() throws ErroSintatico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "procedimento")
                || proximoToken(TipoToken.PALAVRA_RESERVADA, "funcao")) {
            adicionarNo("declProcedimento");
            declProc();
            declProcedimento();
            removeNo();
        }

    }

    private void declProc() throws ErroSintatico {
        adicionarNo("declProc");
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "procedimento")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            consumir(TipoToken.IDENTIFICADOR);
            if (proximoToken(TipoToken.PARENTESES_ABRE)) {
                consumir(TipoToken.PARENTESES_ABRE);
                parametros();
                consumir(TipoToken.PARENTESES_FECHA);

            }
            consumir(TipoToken.PONTOEV);
            if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
                declaracaoVariavel();
            }
            bloco();
            consumir(TipoToken.PONTOEV);
        } else {
            consumir(TipoToken.PALAVRA_RESERVADA, "funcao");
            consumir(TipoToken.IDENTIFICADOR);
            if (proximoToken(TipoToken.PARENTESES_ABRE)) {
                consumir(TipoToken.PARENTESES_ABRE);
                parametros();
                consumir(TipoToken.PARENTESES_FECHA);
            }
            consumir(TipoToken.DOIS_PONTOS);
            tipo();
            consumir(TipoToken.PONTOEV);
            if (proximoToken(TipoToken.PALAVRA_RESERVADA, "var")) {
                declaracaoVariavel();
            }
            bloco();
            consumir(TipoToken.PONTOEV);

        }
        removeNo();
    }

    private void parametros() throws ErroSintatico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            adicionarNo("parametros");
            declVarListProc();
            removeNo();
        }
    }

    private void bloco() throws ErroSintatico {
        adicionarNo("bloco");
        consumir(TipoToken.PALAVRA_RESERVADA, "inicio");
        instrucoes();
        consumir(TipoToken.PALAVRA_RESERVADA, "fim");
        removeNo();
    }

    private void instrucoes() throws ErroSintatico {
        if (isInstrucao()) {
            adicionarNo("instrucoes");
            inst();
            instrucoes();
            removeNo();
        }
    }

    private boolean isInstrucao() {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "se")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "enquanto")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "repita")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "pare")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "continua")) {
            return true;
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "inicio")) {
            return true;
        } else {
            return false;
        }
    }

    private void inst() throws ErroSintatico {
        adicionarNo("inst");
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            consumir(TipoToken.IDENTIFICADOR);
            expressao();
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "se")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            expr();
            consumir(TipoToken.PALAVRA_RESERVADA, "entao");
            corpoEstrut();
            stmIf();
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "enquanto")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            expr();
            consumir(TipoToken.PALAVRA_RESERVADA, "faca");
            corpoEstrut();
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "repita")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            corpoEstrut();
            consumir(TipoToken.PALAVRA_RESERVADA, "ate");
            expr();
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "pare")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "continua")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "inicio")) {
            bloco();
            consumir(TipoToken.PONTOEV);
        } else {
            //erro
        }
        removeNo();
    }

    private void expressoes() throws ErroSintatico {
        if (proximoToken(TipoToken.SEPARADOR)) {
            adicionarNo("expressoes");
            consumir(TipoToken.SEPARADOR);
            expressoes2();
            removeNo();
        } else if (!proximoToken(TipoToken.PARENTESES_FECHA)) {
            adicionarNo("expressoes");
            expr();
            expressoes();
            removeNo();
        }
    }

    private void expressoes2() throws ErroSintatico {
        adicionarNo("expressoes2");
        expr();
        expressoes();
        removeNo();
    }

    private void expressao() throws ErroSintatico {
        adicionarNo("expressao");
        if (proximoToken(TipoToken.ATRIBUICAO)) {
            consumir(TipoToken.ATRIBUICAO);
            expr();
        } else if (proximoToken(TipoToken.COLCHETES_ABRE)) {
            consumir(TipoToken.COLCHETES_ABRE);
            expr();
            consumir(TipoToken.COLCHETES_FECHA);
            consumir(TipoToken.ATRIBUICAO);
            expr();
        } else if (proximoToken(TipoToken.PARENTESES_ABRE)) {
            consumir(TipoToken.PARENTESES_ABRE);
            expressoes();
            consumir(TipoToken.PARENTESES_FECHA);
        } else {
            //para gerar erro de atribuição
           consumir(TipoToken.ATRIBUICAO);
        }
        removeNo();
    }

    private void corpoEstrut() throws ErroSintatico {
        adicionarNo("corpoEstrut");
        inst();
        removeNo();
    }

    private void stmIf() throws ErroSintatico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "senao")) {
            adicionarNo("stmIf");
            consumir(TipoToken.PALAVRA_RESERVADA);
            corpoEstrut();
            removeNo();
        }
    }

    private void declaracaoVariavel() throws ErroSintatico {
        adicionarNo("declaracaoVariavel");
        consumir(TipoToken.PALAVRA_RESERVADA, "var");
        declVarList();
        removeNo();
    }

    private void declVarListProc() throws ErroSintatico {
        adicionarNo("declVarListProc");
        declVarProc();
        declVarListProc2();
        removeNo();
    }

    private void declVarList() throws ErroSintatico {
        adicionarNo("declVarList");
        declVar();
        declVarList2();
        removeNo();
    }

    private void declVarList2() throws ErroSintatico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            adicionarNo("declVarList2");
            declVar();
            declVarList2();
            removeNo();
        }
    }

    private void declVarListProc2() throws ErroSintatico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            adicionarNo("declVarListProc2");
            declVarProc();
            declVarListProc2();
            removeNo();
        }
    }

    private void declVar() throws ErroSintatico {
        adicionarNo("declVar");
        variavel();
        conjuntoIds();
        consumir(TipoToken.DOIS_PONTOS);
        tipo();
        consumir(TipoToken.PONTOEV);
        removeNo();

    }

    private void declVarProc() throws ErroSintatico {
        adicionarNo("declVarProc");
        variavel();
        conjuntoIds();
        consumir(TipoToken.DOIS_PONTOS);
        tipo();
        if (proximoToken(TipoToken.PONTOEV)) {
            consumir(TipoToken.PONTOEV);
            declVarProc();
        }
        removeNo();

    }

    private void conjuntoIds() throws ErroSintatico {
        if (proximoToken(TipoToken.SEPARADOR)) {
            adicionarNo("conjuntoIds");
            consumir(TipoToken.SEPARADOR);
            variavel();
            conjuntoIds();
            removeNo();
        }
    }

    private void declaracaoConstante() throws ErroSintatico {
        adicionarNo("declaracaoConstante");
        consumir(TipoToken.PALAVRA_RESERVADA, "constante");
        declConsList();
        removeNo();
    }

    private void declConsList() throws ErroSintatico {
        adicionarNo("declConsList");
        declCons();
        declConsList2();
        removeNo();
    }

    private void declConsList2() throws ErroSintatico {
        if (proximoToken(TipoToken.IDENTIFICADOR)) {
            adicionarNo("declConsList2");
            declCons();
            declConsList2();
            removeNo();
        }
    }

    private void declCons() throws ErroSintatico {
        adicionarNo("declCons");
        consumir(TipoToken.IDENTIFICADOR);
        if (proximoToken(TipoToken.DOIS_PONTOS)) {
            consumir(TipoToken.DOIS_PONTOS);
            tipo();
            consumir(TipoToken.COMPARADOR, "=");
            valor();
            consumir(TipoToken.PONTOEV);
        } else if (proximoToken(TipoToken.COMPARADOR, "=")) {
            consumir(TipoToken.COMPARADOR, "=");
            valor();
            consumir(TipoToken.PONTOEV);
        } else {
            if (!estadoErro) {
                errosSintaticos.add(new ErroSintatico(lerTokenDaLista(), TipoErroSintatico.TOKEN_ESPERADO));
                estadoErro = true;
            }
        }
        removeNo();
    }

    private void tipo() throws ErroSintatico {
        adicionarNo("tipo");
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "inteiro")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "real")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "char")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "string")) {
            consumir(TipoToken.PALAVRA_RESERVADA);
        } else {
            if (!estadoErro) {
                errosSintaticos.add(new ErroSintatico(lerTokenDaLista(), TipoErroSintatico.TOKEN_ESPERADO));
                estadoErro = true;
            }

        }
        removeNo();
    }

    private void valor() throws ErroSintatico {
        adicionarNo("valor");
        if (proximoToken(TipoToken.LITERAL)) {
            consumir(TipoToken.LITERAL);
        } else {
            unario();
        }
        removeNo();
    }

    private void unario() throws ErroSintatico {
        adicionarNo("unario");
        if (proximoToken(TipoToken.OPERADOR, "+")) {
            consumir(TipoToken.OPERADOR);
        } else if (proximoToken(TipoToken.OPERADOR, "-")) {
            consumir(TipoToken.OPERADOR);
        }
        fator();
        removeNo();
    }

    private void fator() throws ErroSintatico {
        adicionarNo("fator");
        if (proximoToken(TipoToken.NUMERO_INTEIRO)) {
            consumir(TipoToken.NUMERO_INTEIRO);
        } else if (proximoToken(TipoToken.NUMERO_REAL)) {
            consumir(TipoToken.NUMERO_REAL);
        } else if (proximoToken(TipoToken.PARENTESES_ABRE)) {
            consumir(TipoToken.PARENTESES_ABRE);
            expr();
            consumir(TipoToken.PARENTESES_FECHA);
        } else if (proximoToken(TipoToken.LITERAL)) {
            consumir(TipoToken.LITERAL);
        } else if (proximoToken(TipoToken.CHAR)) {
            consumir(TipoToken.CHAR);
        } else {
            variavel();
        }
        removeNo();

    }

    private void expr() throws ErroSintatico {
        adicionarNo("expr");
        exprComparacao();
        expr2();
        removeNo();
    }

    private void expr2() throws ErroSintatico {
        if (proximoToken(TipoToken.PALAVRA_RESERVADA, "ou")) {
            adicionarNo("expr2");
            consumir(TipoToken.PALAVRA_RESERVADA);
            exprComparacao();
            expr2();
            removeNo();
        } else if (proximoToken(TipoToken.PALAVRA_RESERVADA, "e")) {
            adicionarNo("expr2");
            consumir(TipoToken.PALAVRA_RESERVADA);
            exprComparacao();
            expr2();
            removeNo();
        }
    }

    private void exprComparacao() throws ErroSintatico {
        adicionarNo("exprComparacao");
        exprOp();
        exprComparacao2();
        removeNo();
    }

    private void exprComparacao2() throws ErroSintatico {
        if (proximoToken(TipoToken.COMPARADOR)) {
            adicionarNo("exprComparacao2");
            consumir(TipoToken.COMPARADOR);
            exprOp();
            exprComparacao2();
            removeNo();
        }

    }

    private void variavel() throws ErroSintatico {
        adicionarNo("variavel");
        consumir(TipoToken.IDENTIFICADOR);
        variavel2();
        removeNo();
    }

    private void variavel2() throws ErroSintatico {
        if (proximoToken(TipoToken.COLCHETES_ABRE)) {
            adicionarNo("variavel 2");
            consumir(TipoToken.COLCHETES_ABRE);
            exprOp();
            consumir(TipoToken.COLCHETES_FECHA);
            removeNo();
        }
    }

    private void exprOp() throws ErroSintatico {
        adicionarNo("exprOp");
        termo();
        exprOp2();
        removeNo();
    }

    private void exprOp2() throws ErroSintatico {
        if (proximoToken(TipoToken.OPERADOR, "+")) {
            adicionarNo("exprOp2");
            consumir(TipoToken.OPERADOR);
            termo();
            exprOp2();
            removeNo();
        } else if (proximoToken(TipoToken.OPERADOR, "-")) {
            adicionarNo("exprOp2");
            consumir(TipoToken.OPERADOR);
            termo();
            exprOp2();
            removeNo();
        }
    }

    private void termo() throws ErroSintatico {
        adicionarNo("termo");
        unario();
        termo2();
        removeNo();
    }

    private void termo2() throws ErroSintatico {
        if (proximoToken(TipoToken.OPERADOR, "*")) {
            adicionarNo("termo2");
            consumir(TipoToken.OPERADOR);
            unario();
            termo2();
            removeNo();
        } else if (proximoToken(TipoToken.OPERADOR, "/")) {
            adicionarNo("termo2");
            consumir(TipoToken.OPERADOR);
            unario();
            termo2();
            removeNo();
        }
    }

    private Token consumir(TipoToken token) throws ErroSintatico {
        return consumir(token, null);
    }

    private Token consumir(TipoToken tipoToken, String nomeToken) throws ErroSintatico {
        Token tkAtual = lerTokenDaLista();
        Token tk = null;
        if (tkAtual == null) {
            throw new ErroSintatico(tkAtual, TipoErroSintatico.FIM_INESPERADO);
        }

        if (!estadoErro) {
            if (tkAtual.getTipo() == tipoToken && nomeToken == null) {

                tk = consumirTokenDaLista();
                nos.peek().addFilho(new Nofolha(tkAtual));

                return tk;
            } else {
                if (tkAtual.getTipo() == tipoToken && tkAtual.getToken().equalsIgnoreCase(nomeToken)) {
                    tk = consumirTokenDaLista();
                    nos.peek().addFilho(new Nofolha(tkAtual));

                    return tk;
                } else {
                    //      throw new ErroSintatico(tkAtual, new Token(tokenName, tipoToken, 0), TipoErroSintatico.TOKEN_ESPERADO);
                    errosSintaticos.add(new ErroSintatico(tkAtual, new Token(nomeToken, tipoToken, 0), TipoErroSintatico.TOKEN_ESPERADO));
                    estadoErro = true;

                }
            }
        } else {
            eliminarErros(tipoToken, nomeToken);
        }
        return null;
    }

    private void eliminarErros(TipoToken t, String nome) throws ErroSintatico {


        if (t != null) {
            if (t == TipoToken.PONTOEV || t == TipoToken.PARENTESES_FECHA) {
                Token tk = consumirTokenDaLista();
                while (tk.getTipo() != TipoToken.PONTOEV && tk.getTipo() != TipoToken.PARENTESES_FECHA) {
                    tk = consumirTokenDaLista();
                }
                estadoErro = false;
            }
        }
        if (nome != null) {
            if (nome.compareToIgnoreCase("fim") == 0) {
                Token tk = consumirTokenDaLista();
                while (tk.getToken().compareToIgnoreCase("fim") != 0) {
                    tk = consumirTokenDaLista();
                }
                estadoErro = false;
            }
        }
    }

    private boolean proximoToken(TipoToken tipoToken) {
        return proximoToken(tipoToken, null);
    }

    private boolean proximoToken(TipoToken tipoToken, String tokenName) {
        Token atual = lerTokenDaLista();
        if (atual == null) {
            return false;
        }
        if (atual.getTipo() == tipoToken && tokenName == null) {
            return true;
        } else {
            if (atual.getTipo() == tipoToken && atual.getToken().equalsIgnoreCase(tokenName)) {
                return true;
            } else {
                return false;
            }
        }
    }

    private Token consumirTokenDaLista() {
        Token atual;
        try {
            atual = tokens.get(0);
            tokens.remove(0);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }
        return atual;
    }

    private Token lerTokenDaLista() {
        try {
            return tokens.get(0);
        } catch (IndexOutOfBoundsException ex) {
            return null;
        }
    }

    private void adicionarNo(String nome) {
        NoInterno novo = new NoInterno(nome);
        NoInterno pai = nos.peek();
        pai.addFilho(novo);
        novo.setPai(pai);
        nos.push(novo);
    }

    private void removeNo() {
        nos.pop();
    }
}
